#include <buola/chart/cxychart.h>

#include <buola/algorithm/container.h>

#include <buola/image/cfont.h>
#include <buola/image/cgc.h>
#include <buola/utility/random.h>

#include <buola/chart/plots/cxyplot.h>
#include <buola/chart/cxaxis.h>
#include <buola/chart/cyaxis.h>

namespace buola { namespace chart {

CXYChart::CXYChart()
    :   mXAxis(new CXAxis)
    ,   mYAxis(new CYAxis)
    ,   mLegend(new CLegend)
{
}

CXYChart::~CXYChart()
{
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////// Interface //////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CXYChart::AddPlot(PXYPlot pPlot)
{
    mPlots.push_back(pPlot);

    if(pPlot->GetName().empty())
    {
        pPlot->SetName(L"plot "+to_wstring(mPlots.size()));
    }
}

void CXYChart::RemovePlot(PXYPlot pPlot)
{
    mPlots.erase(std::find(mPlots.begin(),mPlots.end(),pPlot));
}

void CXYChart::RemovePlots()
{
    mPlots.clear();
}

void CXYChart::OnExpose(const gui::CExposeEvent &pE)
{
    CXYChartContext lC;
    lC.mChart=this;
    lC.mGC=pE.mGC;
    lC.mRect=CRect_d({0,0},Size());
    lC.mXAxis=mXAxis;
    lC.mYAxis=mYAxis;
    lC.mTraceLock=mTraceLock;
    lC.mLegend=mLegend;
    mLegend->ClearItems();

    if(!Title().empty())
    {
        pE.mGC->SetFont(Font());
        CSize_d lSize=pE.mGC->GetTextExtents(Title());
        pE.mGC->DrawText(CPoint_d((lC.mRect.l+lC.mRect.r)/2,lC.mRect.t+4),Title(),img::ETextPos::HCENTER);
        lC.mRect.t+=lSize.y+8;
    }
    else if(Style(CENTER))
    {
        lC.mRect.t+=30;
    }

    //calculate minimum and maximum in each axis for all plots
    if(!mPlots.size())
    {
        lC.mMinX=0;
        lC.mMinPosX=0.1;
        lC.mMaxX=1;
        lC.mMinY=0;
        lC.mMinPosY=0.1;
        lC.mMaxY=1;
        lC.mExtraSpace={0,0,0,0};
    }
    else
    {
        lC.mMinX=mPlots[0]->GetMinX();
        lC.mMinPosX=mPlots[0]->GetMinPosX();
        lC.mMaxX=mPlots[0]->GetMaxX();
        lC.mMinY=mPlots[0]->GetMinY();
        lC.mMinPosY=mPlots[0]->GetMinPosY();
        lC.mMaxY=mPlots[0]->GetMaxY();
        lC.mExtraSpace=mPlots[0]->GetExtraSpace();

        for(int i=1;i<mPlots.size();i++)
        {
            lC.mMinX=min(lC.mMinX,mPlots[i]->GetMinX());
            lC.mMinPosX=min(lC.mMinPosX,mPlots[i]->GetMinPosX());
            lC.mMaxX=max(lC.mMaxX,mPlots[i]->GetMaxX());
            lC.mMinY=min(lC.mMinY,mPlots[i]->GetMinY());
            lC.mMinPosY=min(lC.mMinPosY,mPlots[i]->GetMinPosY());
            lC.mMaxY=max(lC.mMaxY,mPlots[i]->GetMaxY());
            CRect_d lRect=mPlots[i]->GetExtraSpace();
            lC.mExtraSpace.l=max(lC.mExtraSpace.l,lRect.l);
            lC.mExtraSpace.r=max(lC.mExtraSpace.r,lRect.r);
            lC.mExtraSpace.t=max(lC.mExtraSpace.t,lRect.t);
            lC.mExtraSpace.b=max(lC.mExtraSpace.b,lRect.b);
        }
    }

    mXAxis->ReduceRect(lC);
    mYAxis->ReduceRect(lC);

    //the rect won't change from here, so make final adjustment
    if(Style(THIN))
    {
        lC.mRect.l=round(lC.mRect.l-0.5)+0.5;
        lC.mRect.r=round(lC.mRect.r-0.5)+0.5;
        lC.mRect.t=round(lC.mRect.t-0.5)+0.5;
        lC.mRect.b=round(lC.mRect.b-0.5)+0.5;
    }

    mXAxis->CalcLimits(lC);
    mYAxis->CalcLimits(lC);

///\todo clipping properly
//    pE.mGC->PushState();
//    pE.mGC->Rectangle(lC.mRect.TopLeft(),lC.mRect.Size());
//    pE.mGC->ClipPath();
    pE.mGC->SetSource(FrameColor());
//    pE.mGC->PaintClipRegion();

    for(int i=0;i<mPlots.size();i++)
    {
        mPlots[i]->Draw(lC);
    }

//    pE.mGC->PopState();

    pE.mGC->SetSource(img::CColor(0,0,0));
    if(Style(THIN))
        pE.mGC->SetLineWidth(1.0);
    else
        pE.mGC->SetLineWidth(1.5);

    if(Style(BOX))
    {
        pE.mGC->Path().Rect(lC.mRect.TopLeft(),lC.mRect.Size());
    }
    else
    {
        pE.mGC->Path().MoveTo(CPoint_d(lC.mRect.l,lC.mRect.t));
        pE.mGC->Path().LineTo(CPoint_d(lC.mRect.l,lC.mRect.b));
        pE.mGC->Path().LineTo(CPoint_d(lC.mRect.r,lC.mRect.b));
    }

    pE.mGC->StrokePath();

    pE.mGC->SetStockFont(L"text");

    mYAxis->Draw(lC);
    mXAxis->Draw(lC);

    if(mMarkers.size())
    {
        pE.mGC->SetStockFont(L"textbold");

        PlaceMarkers(pE.mGC);

        const double lRadius=7.5;

        for(int i=0;i<mMarkers.size();i++)
        {
            pE.mGC->SetLineWidth(1.5);
            pE.mGC->Path().Reset();
            pE.mGC->Path().Arc(mMarkers[i]->mRealPos,lRadius);
            pE.mGC->SetSource(img::CColor(1,1,1));
            pE.mGC->FillPath(true);
            pE.mGC->SetSource(mMarkers[i]->mColor);
            pE.mGC->StrokePath();
            double lScale=lRadius/mMarkers[i]->mRadius;
            pE.mGC->Translate(mMarkers[i]->mRealPos);
            pE.mGC->Scale(CSize_d(lScale,lScale));
            pE.mGC->DrawText(CPoint_d(0,0),mMarkers[i]->mText,img::ETextPos::CENTER);
            pE.mGC->Scale(CSize_d(1.0/lScale,1.0/lScale));
            pE.mGC->Translate(-mMarkers[i]->mRealPos);
        }
    }

    pE.mGC->SetStockFont(L"text");
    pE.mGC->SetLineWidth(1.5);

    if(Style(LEGEND))
        mLegend->Draw(lC.mGC,lC.mRect,lC.mTraceLock);

    //storing data for interaction
    mDataRect=lC.mRect;
}

#if 0
void CXYChart::DoDrawHover(img::CGC *pGC,const CRect_d &pRect,const CPoint_d &pPos)
{
    PXYPlot lPlot=mTraceLock;

    if(!lPlot) lPlot=GetPlotAt(pPos);

    if(lPlot)
    {
        CXYChartContext lC;

        lC.mChart=this;
        lC.mGC=pGC;
        lC.mRect=pRect;
        lC.mXAxis=mXAxis;
        lC.mYAxis=mYAxis;
        lC.mTraceLock=mTraceLock;

        lPlot->DrawHover(lC,pPos);

        pGC->SetStockFont(L"text");

        bool lHasName=!lPlot->GetName().empty();
        CPoint_d lDataPos=lPlot->GetNearestPoint(pPos,mXAxis,mYAxis);

        std::wstring lXText(mXAxis->GetTitle()+L": "+to_wstring(lDataPos.x));
        std::wstring lYText(mYAxis->GetTitle()+L": "+to_wstring(lDataPos.y));

        CSize_d lTextSize;
        if(lHasName)
            lTextSize=pGC->GetTextExtents(lPlot->GetName());
        else
        {
            CSize_d lXSize=pGC->GetTextExtents(lXText);
            CSize_d lYSize=pGC->GetTextExtents(lYText);
            lTextSize.x=max(lXSize.x,lYSize.x);
            lTextSize.y=max(lXSize.y,lYSize.y);
        }

        CSize_d lRectSize(lTextSize.x+lTextSize.y+15,(lTextSize.y+5)*
                    (lHasName?3:2)+5);
        CPoint_d lPos(pPos);

        pGC->SetLineWidth(2.0);
        pGC->Rectangle(lPos,lRectSize);
        pGC->SetSourceColor(RGBA(200,200,200,200));
        pGC->FillPath(true);
        pGC->SetSourceColor(RGBA(0,0,0,220));
        pGC->StrokePath();

        double lYPos=lPos.y+5+lTextSize.y/2;

        if(lHasName)
        {
            pGC->SetSourceColor(lPlot->GetColor());
            pGC->Rectangle(CPoint_d(lPos.x+5,lPos.y+5),CSize_d(lTextSize.y,lTextSize.y));
            pGC->FillPath(true);
            pGC->SetSourceColor(CColor(BLACK));
            pGC->StrokePath();

            pGC->DrawText(CPoint_d(lPos.x+10+lTextSize.y,lYPos),lPlot->GetName(),NFont::VCENTER);
            lYPos+=5+lTextSize.y;
        }
        pGC->DrawText(CPoint_d(lPos.x+10,lYPos),lXText,NFont::VCENTER);
        lYPos+=5+lTextSize.y;
        pGC->DrawText(CPoint_d(lPos.x+10,lYPos),lYText,NFont::VCENTER);
    }
}
#endif
#if 0
void CXYChart::Zoom(const CRect_d &pZoomArea)
{
    CRect_d lLimits=pZoomArea;

    FixZoomRect(lLimits);

    if(lLimits.w()>3&&lLimits.w()<mDataRect.w()-1)  //some tolerance
    {
        mXAxis->SetLimits(mXAxis->InverseMap(lLimits.l),mXAxis->InverseMap(lLimits.r));
    }
    if(lLimits.h()>3&&lLimits.h()<mDataRect.h()-1)
    {
        mYAxis->SetLimits(mYAxis->InverseMap(lLimits.b),mYAxis->InverseMap(lLimits.t));
    }
}

void CXYChart::ZoomAll()
{
    mXAxis->SetAutoScale();
    mYAxis->SetAutoScale();
}

PChart CXYChart::GetChartAt(const CPoint_d &pPos,EAction pAction)
{
    if(PChart lSubChart=GetSubChartAt(pPos))
        return lSubChart->GetChartAt(pPos,pAction);

    if(pAction==ACTION_ZOOM)
    {
        if(mXAxis->CanZoom()&&mDataRect.Contains(pPos))
            return this;
    }
    else if(pAction==ACTION_HOVER)
    {
        if(GetStyleBit(MOUSEINFO)&&mDataRect.Contains(pPos))
            return this;
    }
    else if(pAction==ACTION_CLICK)
    {
        return this;
    }

    return nullptr;
}

void CXYChart::FixZoomRect(CRect_d &pRect)
{
    pRect.Normalize();

    pRect.l=max(pRect.l,mDataRect.l);
    pRect.r=min(pRect.r,mDataRect.r);

    pRect.t=max(pRect.t,mDataRect.t);
    pRect.b=min(pRect.b,mDataRect.b);

    double lXDist=pRect.w();
    double lYDist=pRect.h();

    if(lYDist<3||(lXDist>4*lYDist&&lYDist<25))
    {
        pRect.t=mDataRect.t;
        pRect.b=mDataRect.b;
    }
    else if(lXDist<3||(lYDist>4*lXDist&&lXDist<25))
    {
        pRect.l=mDataRect.l;
        pRect.r=mDataRect.r;
    }
}
#endif

void CXYChart::OnButtonDown(const gui::CMouseEvent &pE)
{
    if(mMarkers.size())
    {
        const double lRadius=7.5;

        for(int i=0;i<mMarkers.size();i++)
        {
            if(dist(mMarkers[i]->mRealPos,pE.mPoint)<lRadius)
            {
                sMarkerClicked(mMarkers[i]->mText);
            }
        }
    }
    else if(Style(LEGEND)&&mLegend->Contains(pE.mPoint))
    {
        PPlot lPlot=mLegend->HitTest(pE.mPoint);

        if(lPlot)
        {
            lPlot->SetStyle(lPlot->GetStyle()^CPlot::DISABLED);
            Refresh();
        }
    }
    else
    {
        PXYPlot lPlot=GetPlotAt(pE.mPoint);

        if(lPlot!=mTraceLock)
        {
            if(lPlot) lPlot->Highlight(true);
            if(mTraceLock) mTraceLock->Highlight(false);
            mTraceLock=lPlot;
            sPlotLocked(mTraceLock);
        }
    }
}

void CXYChart::AddMarker(const img::CColor &pColor,const std::wstring &pText,const CPoint_d &pPos)
{
    SMarker &lMarker=construct_back(mMarkers);

    lMarker.mColor=pColor;
    lMarker.mText=pText;
    lMarker.mDataPos=pPos;
}

void CXYChart::RemoveAllMarkers()
{
    mMarkers.clear();
}

PXYPlot CXYChart::GetPlotAt(const CPoint_d &pPos)
{
    double lMinDist=20.0;
    PXYPlot lMinPlot=nullptr;
    for(int i=0;i<mPlots.size();i++)
    {
        double lDist=mPlots[i]->GetDist(pPos,mXAxis,mYAxis);

        if(lDist<lMinDist)
        {
            lMinDist=lDist;
            lMinPlot=mPlots[i];
        }
    }

    return lMinPlot;
}

void CXYChart::PlaceMarkers(img::CGC *pGC)
{
    double lCos[11]={0,1.0,sqrt(3.0)/2.0,0.5,-0.5,-sqrt(3.0)/2.0,-1.0,-sqrt(3.0)/2.0,-0.5,0.5,sqrt(3.0/2.0)};
    double lSin[11]={-1,0.0,0.5,sqrt(3.0)/2.0,-sqrt(3.0)/2.0,0.5,0.0,-0.5,-sqrt(3.0)/2.0,-sqrt(3.0)/2.0,-0.5};

    for(int i=0;i<mMarkers.size();i++)
    {
        CSize_d lSize=pGC->GetTextExtents(mMarkers[i]->mText);
        mMarkers[i]->mExactPos={mXAxis->Map(mMarkers[i]->mDataPos.x),mYAxis->Map(mMarkers[i]->mDataPos.y)};
        mMarkers[i]->mRadius=sqrt(lSize.x*lSize.x+lSize.y*lSize.y)/2+2;
    }
    for(int i=0;i<mMarkers.size();i++)
    {
        const double lRadius=8.0;
        bool lConflict;
        for(double lDist=lRadius*1.2;;lDist+=lRadius/3)
        {
            for(int lAngle=0;lAngle<11;lAngle++)
            {
                mMarkers[i]->mRealPos=mMarkers[i]->mExactPos;
                mMarkers[i]->mRealPos.x+=lDist*lCos[lAngle];
                mMarkers[i]->mRealPos.y+=lDist*lSin[lAngle];

                lConflict=false;

                for(int j=0;j<mMarkers.size();j++)
                {
                    if(dist(mMarkers[i]->mRealPos,mMarkers[j]->mExactPos)<lRadius)
                    {
                        lConflict=true;
                        break;
                    }
                    if(j>=i) continue;
                    if(dist(mMarkers[i]->mRealPos,mMarkers[j]->mRealPos)<2*lRadius)
                    {
                        lConflict=true;
                        break;
                    }
                }

                if(!lConflict) break;
            }

            if(!lConflict) break;
        }
    }
}

/*namespace chart*/ } /*namespace buola*/ }
